﻿using Core.Info;
using Core.Utility.Crypto;
using Core.Utility.Extendsions.CollectionsComparer;
using Core.Utility.Helper;
using Newtonsoft.Json.Linq;
using NPOI.POIFS.Crypt;
using NPOI.XWPF.Usermodel;
using Org.BouncyCastle.Bcpg.OpenPgp;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Reflection.Metadata.Ecma335;
using System.Security.Cryptography;
using System.Security.Policy;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;

namespace Core.Utility.Security
{
    /// <summary>
    /// Api安全助手类
    /// <para>AES加解密参考：<see href="https://blog.csdn.net/wsnbbdbbdbbdbb/article/details/126845388"/></para> 
    /// </summary>
    public class ApiSecurity
    {
        #region 签名/验签       

        /// <summary>
        /// 生成签名
        /// <para>1.通过header获取传递的timestamp（时间戳）、nonce（随机字符串，十分钟内不允许重复）、data_signature（数据验签字符串/消息体签名，用于验证消息体的正确性）、signature（签名字符串）；</para>
        /// <para>2.获取本地的token，将token、timestamp（时间戳）、nonce（随机字符串，十分钟内不允许重复）、data_signature（数据验签字符串/消息体签名，用于验证消息体的正确性） 四个参数进行字典序排序；</para>
        /// <para>3.将token、timestamp（时间戳）、nonce（随机字符串，十分钟内不允许重复）、data_signature（数据验签字符串/消息体签名，用于验证消息体的正确性） </para>四个参数字符串拼接成一个字符串进行sha1加密（加密结果全小写）得到signature（签名字符串）；
        /// </summary>
        /// <param name="token"></param>
        /// <param name="timeStamp">时间戳</param>
        /// <param name="nonce">随机字符串</param>
        /// <param name="data">生成签名额外附加的数据</param>
        /// <returns></returns>
        public static ReturnResultInfo<string> Signature(string? token, long? timeStamp, string? nonce, string? data = "")
        {
            return Signature(token, timeStamp?.ToString(), nonce, data);
        }

        /// <summary>
        /// 生成签名
        /// <para>1.通过header获取传递的timestamp（时间戳）、nonce（随机字符串，十分钟内不允许重复）、data_signature（数据验签字符串/消息体签名，用于验证消息体的正确性）、signature（签名字符串）；</para>
        /// <para>2.获取本地的token，将token、timestamp（时间戳）、nonce（随机字符串，十分钟内不允许重复）、data_signature（数据验签字符串/消息体签名，用于验证消息体的正确性） 四个参数进行字典序排序；</para>
        /// <para>3.将token、timestamp（时间戳）、nonce（随机字符串，十分钟内不允许重复）、data_signature（数据验签字符串/消息体签名，用于验证消息体的正确性） </para>四个参数字符串拼接成一个字符串进行sha1加密（加密结果全小写）得到signature（签名字符串）；
        /// </summary>
        /// <param name="token"></param>
        /// <param name="timeStamp">时间戳</param>
        /// <param name="nonce">随机字符串</param>
        /// <param name="data">生成签名额外附加的数据</param>
        /// <returns></returns>
        public static ReturnResultInfo<string> Signature(string? token,string? timeStamp,string? nonce,string? data = "")
        {
            ReturnResultInfo<string> res = new ReturnResultInfo<string>(message:"生成签名失败");
            try
            {
                ArrayList dictionaryParameterList = new ArrayList();
                dictionaryParameterList.Add(token);
                dictionaryParameterList.Add(timeStamp);
                dictionaryParameterList.Add(nonce);
                dictionaryParameterList.Add(data);
                dictionaryParameterList.Sort(new DictionarySort());
                string dictionaryParameterSortString = string.Join("", dictionaryParameterList.ToArray());
                string signature = CryptoFactoryHelper.SHA1(dictionaryParameterSortString, resultToUpperOrLower: false);
                res.SetSuccess(signature, "生成签名成功");
            }
            catch(Exception ex)
            {
                res.SetException("生成签名失败", ex);
            }
            return res;
        }

        /// <summary>
        /// 验签，验签原理：传入签名涉及的参数，进行签名，将签名后的结果与传入的签名进行全等判断，一致返回true，否则返回false
        /// </summary>
        /// <param name="hashSignature">验证签名字符串</param>
        /// <param name="token"></param>
        /// <param name="timeStamp">时间戳</param>
        /// <param name="nonce">随机字符串</param>
        /// <param name="data">生成签名额外附加的数据</param>
        /// <returns></returns>
        public static ReturnResultInfo VerifySignature(string? hashSignature, string? token, long? timeStamp, string? nonce, string? data = "")
        {
            return VerifySignature(hashSignature,token, timeStamp?.ToString(), nonce, data);
        }

        /// <summary>
        /// 验签，验签原理：传入签名涉及的参数，进行签名，将签名后的结果与传入的签名进行全等判断，一致返回true，否则返回false
        /// </summary>
        /// <param name="hashSignature">验证签名字符串</param>
        /// <param name="token"></param>
        /// <param name="timeStamp">时间戳</param>
        /// <param name="nonce">随机字符串</param>
        /// <param name="data">生成签名额外附加的数据</param>
        /// <param name="timeout">为null或&lt;0不验证超时</param>
        /// <returns></returns>
        public static ReturnResultInfo VerifySignature(string? hashSignature, string? token, string? timeStamp, string? nonce, string? data = "",long? timeout = null)
        {
            ReturnResultInfo res = new ReturnResultInfo(message:"验签失败");
            try
            { 
                if(timeout != null && timeout.GetValueOrDefault(0) > 0)
                {
                    DateTime timeStampDateTime = DateTimeHelper.ToDateTime(timeStamp ?? "");
                    if(timeStampDateTime.AddSeconds(timeout.GetValueOrDefault(0)) < AppTime.Now)
                    {
                        res.SetFailure($"验签失败，签名时间戳已超过：{timeout}s");
                        return res;
                    }
                }

                var signatureInfo = Signature(token, timeStamp, nonce, data);
                if (signatureInfo.Successed == false)
                {
                    res.SetFailure("验签时生成原始签名失败",info:signatureInfo.Info);
                }
                else
                {
                    //System.Console.WriteLine(hash);
                    if (hashSignature == signatureInfo.Data)
                    {
                        res.SetSuccess("验签成功");
                    }
                    else
                    {
                        res.SetFailure("签名无效");
                    }
                }
            }
            catch (Exception ex)
            {
                res.SetException("验签失败", ex);
            }
            return res;
        }
        #endregion 签名/验签

        #region AES加密
        /// <summary>
        /// AES加密
        /// </summary>
        /// <param name="input"></param>
        /// <param name="encodingAESKey">消息加解密 Key，长度固定为 43 个字符，从 a-z,A-Z,0-9 共 62 个字符中选取</param>
        /// <param name="appId">应用ID</param>
        /// <returns></returns>
        public static ReturnResultInfo<string> AESEncrypt(string? input, string? appId, string? encodingAESKey)
        {
            ReturnResultInfo<string> res = new ReturnResultInfo<string>(message:"AES加密失败");
            try
            {
                if (encodingAESKey?.Length != 43)
                {
                    res.SetFailure("AES加密失败：EncodingAESKey长度必须为43位");
                }
                else
                {
                    //EncodingAESKey 尾部填充一个字符的 "=", 用 Base64_Decode 生成 32 个字节的 AESKey
                    byte[] aesKey;
                    aesKey = Convert.FromBase64String(encodingAESKey + "=");

                    //截取aesKey前16位作为IV
                    byte[] iv = new byte[16];
                    Array.Copy(aesKey, iv, 16);
                    //16 字节的随机字符串
                    string randCode = RandomHelper.NumChar(16); //CreateRandCode(16);
                    byte[] bRand = Encoding.UTF8.GetBytes(randCode);
                    byte[] bAppid = Encoding.UTF8.GetBytes(appId ?? "");
                    byte[] btmpMsg = Encoding.UTF8.GetBytes(input ?? "");
                    byte[] bMsgLen = BitConverter.GetBytes(HostToNetworkOrder(btmpMsg.Length));

                    //创建可容纳所有参数的 byte[]
                    byte[] bMsg = new byte[bRand.Length + bMsgLen.Length + bAppid.Length + btmpMsg.Length];

                    Array.Copy(bRand, bMsg, bRand.Length);
                    Array.Copy(bMsgLen, 0, bMsg, bRand.Length, bMsgLen.Length);
                    Array.Copy(btmpMsg, 0, bMsg, bRand.Length + bMsgLen.Length, btmpMsg.Length);
                    Array.Copy(bAppid, 0, bMsg, bRand.Length + bMsgLen.Length + btmpMsg.Length, bAppid.Length);
                    string resEncrypt = AesEncrypt(bMsg, iv, aesKey);
                    res.SetSuccess(resEncrypt);
                }
            }
            catch(Exception ex)
            {
                res.SetException("AES加密失败", ex);
            }
            return res;
        }

        /// <summary>
        /// AES加密
        /// </summary>
        /// <param name="input"></param>
        /// <param name="iv"></param>
        /// <param name="key"></param>
        /// <returns></returns>
        private static string AesEncrypt(byte[] input, byte[] iv, byte[] key)
        {
            var aes = Aes.Create();
            aes.Mode = CipherMode.CBC;
            aes.Padding = PaddingMode.PKCS7;
            aes.BlockSize = 128;
            aes.KeySize = 256;
            aes.Key = key;
            aes.IV = iv;
            var transform = aes.CreateEncryptor();
            byte[] outputByte = transform.TransformFinalBlock(input, 0, input.Length);
            string output = Convert.ToBase64String(outputByte);
            return output;
        }
        #endregion AES加密

        #region AES解密


        /// <summary>
        /// AES解密
        /// </summary>
        /// <param name="input">密文</param>
        /// <param name="encodingAESKey"></param>
        /// <returns></returns>
        public static ReturnResultInfo<string> AESDecrypt(string? input, string? encodingAESKey)
        {
            ReturnResultInfo<string> res = new ReturnResultInfo<string>(message: "AES解密失败");
            try
            {
                string? appId = null;
                return AESDecrypt(input, encodingAESKey, ref appId);
            }
            catch (Exception ex)
            {
                res.SetException("AES解密失败", ex);
            }
            return res;
        }

        /// <summary>
        /// AES解密
        /// </summary>
        /// <param name="input">密文</param>
        /// <param name="encodingAESKey"></param>
        /// <param name="appId">应用Id</param>
        /// <returns></returns>
        public static ReturnResultInfo<string> AESDecrypt(string? input, string? encodingAESKey, ref string? appId)
        {
            ReturnResultInfo<string> res = new ReturnResultInfo<string>(message: "AES解密失败");
            try
            {
                if (encodingAESKey?.Length != 43)
                {
                    res.SetFailure("AES解密失败：EncodingAESKey长度必须为43位");
                }
                else
                {
                    byte[] Key;
                    Key = Convert.FromBase64String(encodingAESKey + "=");
                    byte[] Iv = new byte[16];
                    Array.Copy(Key, Iv, 16);
                    byte[] btmpMsg = AESDecrypt(input, Iv, Key);

                    int len = BitConverter.ToInt32(btmpMsg, 16);
                    len = IPAddress.NetworkToHostOrder(len);

                    byte[] bMsg = new byte[len];
                    byte[] bAppid = new byte[btmpMsg.Length - 20 - len];
                    Array.Copy(btmpMsg, 20, bMsg, 0, len);
                    Array.Copy(btmpMsg, 20 + len, bAppid, 0, btmpMsg.Length - 20 - len);
                    string oriMsg = Encoding.UTF8.GetString(bMsg);
                    appId = Encoding.UTF8.GetString(bAppid);
                    res.SetSuccess(oriMsg);
                }
            }
            catch(Exception ex)
            {
                res.SetException("AES解密失败", ex);
            }
            return res;
        }

        /// <summary>
        /// AES解密
        /// </summary>
        /// <param name="input"></param>
        /// <param name="iv"></param>
        /// <param name="key"></param>
        /// <returns></returns>
        private static byte[] AESDecrypt(string? input, byte[] iv, byte[] key)
        {
            var aes = Aes.Create();
            aes.Mode = CipherMode.CBC;
            aes.Padding = PaddingMode.PKCS7;
            aes.BlockSize = 128;
            aes.KeySize = 256;
            aes.Key = key;
            aes.IV = iv;
            var transform = aes.CreateDecryptor();
            //byte[] outputBytes = transform.TransformFinalBlock(inputBytes, 0, input?.Length ?? 0);
            byte[] inputBytes = Convert.FromBase64String(input ?? "");
            byte[] inputDecryptBytes = transform.TransformFinalBlock(inputBytes, 0, inputBytes.Length);
            return inputDecryptBytes;          
        }

        #endregion AES解密

        /// <summary>
        /// 网络字节序 - 排序
        /// </summary>
        /// <param name="inval"></param>
        /// <returns></returns>
        public static int HostToNetworkOrder(int inval)
        {
            int outval = 0;
            for (int i = 0; i < 4; i++)
                outval = (outval << 8) + ((inval >> (i * 8)) & 255);
            return outval;
        }

        #region 生成随机数相关
        /// <summary>
        /// 生成随机数
        /// </summary>    
        public static string GenerateNonce()
        {
            return Guid.NewGuid().ToString().Replace("-", "");
        }

        /// <summary>
        /// 生成时间戳
        /// </summary>    
        public static long GenerateTimeStamp()
        {
            return AppTime.NowUnixTime;
        }

        /// <summary>
        /// 生成时间戳
        /// </summary>    
        public static string GenerateTimeStampString()
        {
            return GenerateTimeStamp().ToString();
        }

        /// <summary>
        /// 生成Token
        /// </summary>    
        public static string GenerateToken()
        {
            Random rd = new Random();
            var length = rd.Next(3, 32);
            return RandomHelper.NumChar(length);
        }

        /// <summary>
        /// 生成EncodingAESKey
        /// </summary>    
        public static string GenerateEncodingAESKey()
        {
            return RandomHelper.NumChar(43);
        }

        /// <summary>
        /// 生成AppId
        /// </summary>
        /// <param name="prefix"></param>
        /// <returns></returns>
        public static string GenerateAppId(string prefix = "")
        {
            string guid = Guid.NewGuid().ToString().Replace("-","");
            string random = RandomHelper.NumChar(128);
            string timeStamp = AppTime.NowUnixTimeMs.ToString();
            string md5Hash = CryptoFactoryHelper.MD5(guid + random + prefix, timeStamp, 4, false);
            return $"{prefix}{md5Hash}";
        }


        /// <summary>
        /// 生成AppKey
        /// </summary>    
        public static string GenerateAppKey()
        {
            string guid = Guid.NewGuid().ToString().Replace("-", "");
            string random = RandomHelper.NumChar(32);
            string timeStamp = AppTime.NowUnixTimeMs.ToString();
            string md5Hash = CryptoFactoryHelper.SHA1(guid + random, timeStamp, 2, false);
            return md5Hash;
        }

        /// <summary>
        /// 生成AppSecret
        /// </summary>    
        public static string GenerateAppSecret()
        {
            string guid = Guid.NewGuid().ToString().Replace("-", "");
            string random = RandomHelper.NumChar(64);
            string timeStamp = AppTime.NowUnixTimeMs.ToString();
            string md5Hash = CryptoFactoryHelper.SHA1(guid + random, timeStamp, 3, false);
            return md5Hash;
        }
        #endregion 生成随机数相关
    }
}
